package com.google.code.xconf.schema.definition;

import com.google.code.xconf.Property;
import com.google.code.xconf.PropertyGroup;
import com.google.code.xconf.schema.SchemaLayoutException;
import com.google.code.xconf.schema.SchemaSettings;
import com.google.code.xconf.schema.SchemaDefinition;
import com.google.code.xconf.schema.definition.value.options.CompoundOptions;
import com.google.code.xconf.schema.definition.value.options.ExtendedBooleanModes;
import com.google.code.xconf.schema.definition.value.options.LogicalBooleanModes;
import com.google.code.xconf.schema.definition.value.options.NumbericBooleanModes;
import com.google.code.xconf.schema.definition.value.options.NumericRange;
import com.google.code.xconf.schema.definition.value.options.Options;
import com.google.code.xconf.schema.definition.value.options.ShortLBooleanModes;
import com.google.code.xconf.schema.definition.value.options.SpecifiedOptionsList;
import com.google.code.xconf.schema.definition.value.options.ToggleBooleanModes;
import com.google.code.xconf.schema.definition.value.Blob;
import com.google.code.xconf.schema.definition.value.Numeric;
import com.google.code.xconf.schema.definition.value.ValueType;
import static com.google.code.xconf.schema.SchemaConstants.*;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class PropertyValueDefinition extends SchemaDefinition {

    private boolean optional;
    private ValueType valueType;
    private Options options;

    public PropertyValueDefinition(PropertyGroup delegate, SchemaSettings schemaSettings) {
        super(PROPERTY_VALUE, delegate, schemaSettings);
        configure();
    }

    private void configure() {
        settings = settings.snapshot();
        settings.setPropertyValueCaseSensitive(resolvePropertyValueCaseSensitive());
        optional = resolveOptional();
        valueType = resolveValueType();
        options = resolveOptions();
    }

    @Override
    protected List<String> getValidNodeNames() {
        return Arrays.asList(OPTIONS, VALUE_TYPE, OPTIONAL, PROPERTY_VALUE_CASE_SENSITIVE);
    }

    public boolean matches(String expr) {
        boolean matches = true;
        if (valueType != null) {
            matches = valueType.matches(expr);
        }
        Options options = resolveOptions();
        if (options != null) {
            matches = matches && options.matches(expr, isPropertyValueCaseSensitive());
        }
        return matches;
    }

    public Options getOptions() {
        return options;
    }

    private Options resolveOptions() {
        Property property = definition.getLastProperty(OPTIONS);
        if (property == null) {
            return null;
        }
        Set<Options> options = new HashSet<>();
        Set<String> specifiedOptions = new HashSet<>();
        for (String value : property.getValues()) {
            if (ToggleBooleanModes.class.getSimpleName().equals(value)) {
                options.add(new ToggleBooleanModes());
            } else if (NumbericBooleanModes.class.getSimpleName().equals(value)) {
                options.add(new NumbericBooleanModes());
            } else if (LogicalBooleanModes.class.getSimpleName().equals(value)) {
                options.add(new LogicalBooleanModes());
            } else if (ShortLBooleanModes.class.getSimpleName().equals(value)) {
                options.add(new ShortLBooleanModes());
            } else if (ExtendedBooleanModes.class.getSimpleName().equals(value)) {
                options.add(new ExtendedBooleanModes());
            } else if (NumericRange.isRangeExpr(value)) {
                options.add(new NumericRange(value));
            } else {
                specifiedOptions.add(value);
            }
        }
        if (!specifiedOptions.isEmpty()) {
            SpecifiedOptionsList specifiedOptionsList = new SpecifiedOptionsList(specifiedOptions);
            if (options.isEmpty()) {
                return specifiedOptionsList;
            } else {
                options.add(specifiedOptionsList);
                return new CompoundOptions(options);
            }
        } else {
            if (options.size() == 1) {
                return options.iterator().next();
            } else {
                return new CompoundOptions(options);
            }
        }
    }

    private ValueType resolveValueType() {
        String value = resolveDefinitionPropertyString(VALUE_TYPE, true);
        if (value != null) {
            if (Numeric.class.getSimpleName().equalsIgnoreCase(value)) {
                return new Numeric();
            } else if (!Blob.class.getSimpleName().equalsIgnoreCase(value)) {
                throw new SchemaLayoutException("Invalid ValueType '" + value + "'");
            }
        }
        return new Blob();
    }

    public boolean isOptional() {
        return optional;
    }

    private boolean resolveOptional() {
        return resolveDefinitionPropertyBoolean(OPTIONAL, true, definition.containsValue(OPTIONAL));
    }

    private boolean resolvePropertyValueCaseSensitive() {
        return resolveDefinitionPropertyBoolean(PROPERTY_VALUE_CASE_SENSITIVE, true, isPropertyValueCaseSensitive());
    }

}
